(引用自 https://www.flickr.com/photos/strollers/12465549/)
想必沒吃過也聽過看過迴轉壽司吧,壽司師傅站在工作檯前,拿著出不同的食材,製作出一盤盤的美味的壽司,有鮭魚握壽司、海膽壽司、鮭魚卵軍艦壽司等等,做好的食材被放到迴轉台上送到按照順序坐好的食客面前。
每個食客都有自己心中想要吃的那一個種類,當壽司轉到面前的時候,看一下眼前這盤是不是自己所想要的,是的話就取下來盡情享用(喔,對這盤也有興趣的人就只好等下一盤了),至於沒有興趣的,就讓它過去吧,它總會遇到對的人把它給接收了。
我們今天要討論的 Chain of Responsibility 模式就跟迴轉壽司一樣,有人在製造需求,然後有一群人們把需求一個一個轉傳下去,只把自己有興趣、能處理的需求拿來處理。當然座位不是隨便你坐的,是店家幫忙安排的。
上面的迴轉壽司範例,想必大家看完都肚子餓了吧,忍耐一下,看完就可以開飯了。
讓多個物件都有機會處理某一訊息,以降低訊息發送者和接收者之間的耦合關係。它將接收者物件串連起來,讓訊息流經其中,直到被處理了為止。
從定義中和迴轉壽司的例子中,我們發現所有的接收者應該都會擁有同樣的公開介面,而且每一個接收者都會有一個內部用來判定是否要處理訊息的標準,小提醒一下,即使是來者不拒也是一種判斷標準喔。然而,接收者的座位順序是由主程式或者是由發送者來決定的。於是我們可以來看看下面的 UML 類別圖了。
class SushiMaker
attr_reader :sushi_lover
def initialize
h1 = SalmonSushiLover.new
h2 = TunaSushiLover.new
h1.next_sushi_lover = h2
sushi_lover = h1
end
def start
sushi = make_susshi_with_random_material
sushi_lover.eat(sushi)
end
end
# Handler
class SushiLover
attr_reader :next_sushi_lover
def next_sushi_lover=(next_lover)
self.next_sushi_lover
end
def eat(sushi)
if next_sushi_lover
next_sushi_lover.eat(sushi)
end
end
end
# Concrete Handlers
class SalmonSushiLover < SushiLover
def eat(sushi)
if eat?(sushi)
puts 'I love Salmon!'
else
super
end
end
private
def eat?(sushi)
sushi.topping == :salmon
end
end
class TunaSushiLover < SushiLover
def eat(sushi)
if eat?(sushi)
puts 'I love Tuna!'
else
super
end
end
private
def eat?(sushi)
sushi.topping == :tuna
end
end
不知道怎地,寫完範例總覺得兩個食客是被強迫餵食。 XD
從迴轉壽司的範例中,其實會注意到,食客其實並不知道壽司師傅接下來會做出什麼壽司,可能是鮭魚也可能是玉子燒。在實際工程場景中,如果我們的程式需要針對不同的訊息有不同的對應行為,可是在執行中卻無法提前知道會有什麼樣的需求時,就滿適合使用 Chain of Responsibility 來處理。有沒有覺得很熟悉?是的,網路請求就很符合這個場景。以 Ruby 開發網路程式為例,在 Ruby 的世界裡,有個很經典的網路程式架構叫做 Rack,一個 Rack 的程式,基本上會加入一系列 middleware,而每一個 middleware 都實作了 call()
這個方法,當請求進來的時候,Rack 主程式會依照註冊的順序,把請求傳遞進去,讓每一個 middleware 去處理。
我們可以自己控制接收者的順序以達到我們想要的處理流程;而且透過 Chain of Responsibility 模式,我們的程式可以更容易的加入新的行為,卻不會影響既有的行為,這也是 Open-Close Principle 所要求的;另外因為接收者只需要對自己有能力處理的訊息做處理,所以這邊也符合了 Single Responsibility Principle.
如果我們忘記把某個接收者串連起來,或者有某種類型的訊息沒有對應的接收者,那麼我們的程式就會漏掉某些請求。
當我們的程式需要對不同的訊息有不同的處理方式,但是對外的介面是固定介面的情況下,我們可能會發現自己的主程式變得異常的長,站遠一點還會看到波動拳(一堆 if - elsif - elsif - elsif ... - else),這種情況其實就是我們的程式跟處理訊息的物件過度耦合了,這個時候我們其實可以思考是不是適合使用 Chain of Responsibility 來讓我們的程式更容易維護,也更容易增加功能,今天的介紹就到這邊啦,大家來去吃迴轉壽司吧!
作者:Yenting